Redis Cluster 是为了解决什么问题?
虽然Redis
的单机性能已经足够高了,但是面对互联网企业动辄几百G 的缓存数据,此时性能再好的单机也无法满足业务需求了。在这种情况下 Redis
官方推出了集群模式:Redis Cluster
。Redis Cluster
管理的是一组Redis
,每个Redis
称为一个节点。由于可以在集群中任意扩展节点,所以Cluster
主要是提高了缓存系统扩展性,当然还提升了性能。我们知道,衡量一个系统有很多个指标,主要是包括性能,可用性,可扩展性,从这些方面,我们可以总结一下 :
Redis
主从复制,提升的是读写性能以及数据的安全性(备份);Redis Sentinel
哨兵,监控所有节点的状况,提升的是可用性;Redis Cluster
集群,提升的是可扩展性,以及性能;
Redis Cluster 介绍
Redis
集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分成16384
个 槽(slot
),然后把所有的键值对均匀的分散到这些槽位中,每个节点可以存储不同的槽数。 简单的经典架构如下图:
上图展示的是一种主从复制模型,包含三个master
,每个master
有一个slave
,每个master
分得不同的槽位。一个槽位(坑位)可以存放多个键值对,当2号master
节点挂掉的时候,整个集群就会以为缺少5462-10922这个范围的槽而不可用,而此时集群会选举slave
作为主节点,整个集群便不会因为槽找不到而不可用了。但是当主从节点都挂掉的时候,此时集群是不可用的。
数据一致性
Redis 并不能保证数据的强一致性,所谓强一致性,是指主从节点在任何时候都是一样的,Redis 无法保证这一点。考虑以下两种情况:
1.主从节点复制延迟:
主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。
2.出现网络分区:
举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .
Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.
注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项:
搭建集群环境
这里我们创建包含6台机器的集群,采取三主三从的模式,每个master
有一个slave
,端口分配分别是6380,6381,6382,6283,6384,6385
. 这里没有采用docker
环境,因为官方教程上说需要docker
开启host
的网络连接模式,但是这种模式仅支持linux
系统,因此这里采用的是直接redis-server xxx/redis-x.conf
方式启动六台机器。目录结构如下。
这六个redis
实例启动后,还没有组建成一个集群环境,官方提供了一个redis-trib.rb
脚本来搭建集群,所以需要在Mac
上先安装ruby
。1
brew install ruby
安装ruby
的 redis 支持1
gem install redis
上面的redis-trib.rb
文件是在redis
的源码src
目录里,如果找不到,可以去 github
,redis
项目 里面去找。另外这个文件还要增加可执行权限。1
chmod +x redis-trib.rb
然后就可以用这个脚本来构建集群了1
./redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6382 127.0.0.1:6384 127.0.0.1:6385 127.0.0.1:6381 127.0.0.1:6383
replicas 1
表示一主一从的模式,后面跟六个实例ip
和端口。
启动完成后,会显示槽分配完成
此时我们就拥有一个redis
集群了!
连接集群1
redis-cli -c -p 6380
此时显示出 key 被分配到了哪一个槽
此时包含一个从机
cluster API
可以使用redis-cli
直接连接后(要加上-c
参数),查看集群信息
cluster info
:查看集群信息cluster nodes
查看所有节点信息
集群机制说明
####节点是如何加入集群的?
通过cluster meet
指令,连接到集群中的任意一个节点后,比如1
cluster meet 127.0.0.1 7001
意思是把7001
加入到当前的集群中,但是7001
目前是没有分配槽的。
这个命令是如何实现的呢?大致过程是这样,比如 A和 B,B 此时想要加入加入节点,那么 A和 B 先通信,二者确认无误后,A 会通过Gossip
协议把节点 B 的信息传播给其他节点,让其他节点和 B 进行通信握手,最终 B 节点就会被集群中的所有节点认识。
槽信息是如何保存的?
首先每个节点中有这样一个结构来记录槽信息:
这样判断哪个槽是否在当前节点上时,复杂度为O(1)
.
那如何知道哪个槽在哪个节点上?
还有一个数据结构在记录,是clusterState
这个slots
就记录了每个槽对应那个节点!这样在查询时复杂度也是O(1)
.
不得不佩服,这样存储非常高效!
####集群模式下命令如何执行的?
当客户端发起增啥改查命令时,节点需要做以下事情。
- 检查键所在的槽是否在当前节点,如果是,执行执行命令。
- 如果槽不在当前节点,节点会向客户端返回
moved
错误,引导客户端转向正确的节点。
####节点数据库和单机数据库有啥区别?
集群节点对键值对的保存以及过期时间的处理完全一样。一个区别是节点只能使用0号数据库,单机可以设置多个数据库。
####重新分片如何实现的?
重新分片是指把一个节点的槽位拿一部分出来,放到新节点上,因为新加入的节没有槽位。
实现原理:
ASK 错误
考虑一种情况,要把节点 A 的部分槽位迁移到 B上,此时有一部分已经迁移到 B 上了,此时客户端开始请求,那么节点 A 会先查找是否存在本身节点上,如果不存在,会返回ASK
错误,并引导客户端向 B 请求。
过程如下:
故障转移
当主节点不可用时,从节点会成为主节点。
故障检测
集群中的每个节点都会定期向集群中的其他节点发送PING
信息,检测对方是否在线。如果没在规定时间内返回PONG
信息,那么就会被标记为疑似下线(PFAIL
).如果在一个集群中,半数以上的主节点都将某个主节点标记为疑似下线,那么这个主节点将会被标记为下线(FAIL
)。
主节点下线后,故障转移开始,其他的主节点开始选举下线主节点的某个从节点作为 主节点,当从节点获取的票数>=N/2+1
时,该从节点升级为主节点。选举算法基于Raft
算法。